/**
 * Copyright 2016 Google Inc. All Rights Reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/license-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the license.
 */

'use strict';
var Cursor = require('./cursor');
var stateMap = require('./stateMap');
var diff = require('../util/diff');

/** 
 * Provides a cursor which simulates an open-palm hand. 
 * 
 * The hand shape is randomly generated and is modeled on results from physical testing. 
 * The timing of the events generated by the simulated hand is fuzzed to futher emulate the 
 * variable timing that each part of a hand exhibits when it's mashed on a grid of buttons. 
 */
var CursorHand = module.exports = function() {
	Cursor.call(this);
	this.cursorPoints = {};
}

// Inherit from Cursor
CursorHand.prototype = new Cursor();
CursorHand.prototype.constructor = CursorHand;

// The amount of time in milliseconds over which multiple button inputs can take place from a single 
// cursor action. This is used to simulate the uneven pressure and timing of a human hand against 
// the buttonwall.
var maxFuzzMs = 40;

/**
 * Function called when the cursor's input starts. This function generates a new 5x5 hand shape,
 * centered on a 2x2 or 3x2 rectangle palm. Points in the initial palm are then iteratively grown
 * outwards to simulate fingers
 */
CursorHand.prototype.onInputStart = function(position) {
	// Palm can be a 2x2 or 3x2 rectangle of points
	var palmX = Math.random() > 0.5 ? 2 : 1;
	var palmY = 2;
	var palmW = palmX > 1 ? 2 : randInt(2, 3);
	var palmH = 2;
	for (var y = palmY; y < palmY + palmH; y++) {
		for (var x = palmX; x < palmX + palmW; x++) {
			var keyCursor = stateMap.keyFromPosition({x: x, y: y});
			var keyActive = stateMap.keyFromPosition({
				x: position.x + x,
				y: position.y + y
			});

			this.activePoints[keyActive] = true;
			this.cursorPoints[keyCursor] = true;
		}	
	}
	
	// Iteratively grow some fingers (more points) out of the existing set of points
	for (var i = 0; i < 3; i++) {
		// Only grow from the middle 3x3 points
		for (var y = 1; y < 3; y++) {
			for (var x = 1; x < 3; x++) {
				var keyActive = stateMap.keyFromPosition({
					x: x + position.x,
					y: y + position.y
				});

				// Only grow from existing points
				if (this.activePoints.hasOwnProperty(keyActive)) {
					// Growth direction is one unit towards any of the 8 points surrounding the current point
					var growth = {
						x: x + randInt(-1, 1),
						y: y + randInt(-1, 1)
					};

					// Store grown point's local position for redrawing during onInputMove()
					this.cursorPoints[stateMap.keyFromPosition(growth)] = true;
					
					// Store grown point's position offset by the cursor position
					this.activePoints[stateMap.keyFromPosition({
						x: growth.x + position.x,
						y: growth.y + position.y
					})] = true;
				}
			}
		}
	}

	// Fuzz the timing when adding the points to the stateMap
	for (var point in this.activePoints) {
		stateMap.set(stateMap.keyToPosition(point), true, Math.random() * maxFuzzMs);
	}

	function randInt(min, max) { 
		return Math.floor(Math.random() * (max - min + 1)) + min;
	}
}

/**
 * Function called when the cursor is moved. Points which leave the activePoints map are removed
 * from the button state map, and points that are new are added.
 */
CursorHand.prototype.onInputMove = function(lastPosition, currentPosition) {
	// Make map of current active points by translating each cursor point by the current position
	var currentActivePoints = {};
	for (var point in this.cursorPoints) {
		var cursorPointPos = stateMap.keyToPosition(point);
		
		var position = {
			x: cursorPointPos.x + currentPosition.x,
			y: cursorPointPos.y + currentPosition.y
		};

		// The current active points is a map of each cursor point offset by currentPosition
		currentActivePoints[stateMap.keyFromPosition(position)] = true;
	}

	// Diff the previous active points against the current active points
	var diffPoints = diff(this.activePoints, currentActivePoints);

	// Any points which are in the current set and not in the previous set add a position to the state map
	diffPoints.added.forEach(function(key) {
		stateMap.set(stateMap.keyToPosition(key), true);
		this.activePoints[key] = true;
	}, this);

	// Any points which are in the previous set and not in the current set remove a position from the state map
	diffPoints.removed.forEach(function(key) {
		stateMap.set(stateMap.keyToPosition(key), false);
		delete this.activePoints[key];
	}, this);
}

/**
 * Function called when the cursor is released. All active buttons are removed from the state map.
 */
CursorHand.prototype.onInputEnd = function(position) {
	for (var point in this.activePoints) {
		stateMap.set(stateMap.keyToPosition(point), false, Math.random() * maxFuzzMs);
	}	

	this.cursorPoints = {};
	this.activePoints = {};
}